/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.qihoo.qsql.org.apache.calcite.rel.externalize;

import org.apache.calcite.avatica.util.TimeUnitRange;
import com.qihoo.qsql.org.apache.calcite.sql.JoinConditionType;
import com.qihoo.qsql.org.apache.calcite.sql.JoinType;
import com.qihoo.qsql.org.apache.calcite.sql.SqlExplain;
import com.qihoo.qsql.org.apache.calcite.sql.SqlExplainFormat;
import com.qihoo.qsql.org.apache.calcite.sql.SqlExplainLevel;
import com.qihoo.qsql.org.apache.calcite.sql.SqlInsertKeyword;
import com.qihoo.qsql.org.apache.calcite.sql.SqlJsonConstructorNullClause;
import com.qihoo.qsql.org.apache.calcite.sql.SqlJsonQueryWrapperBehavior;
import com.qihoo.qsql.org.apache.calcite.sql.SqlJsonValueEmptyOrErrorBehavior;
import com.qihoo.qsql.org.apache.calcite.sql.SqlMatchRecognize;
import com.qihoo.qsql.org.apache.calcite.sql.SqlSelectKeyword;
import com.qihoo.qsql.org.apache.calcite.sql.fun.SqlTrimFunction;

import com.google.common.collect.ImmutableMap;

/** Registry of {@link Enum} classes that can be serialized to JSON.
 *
 * <p>Suppose you want to serialize the value
 * {@link SqlTrimFunction.Flag#LEADING} to JSON.
 * First, make sure that {@link SqlTrimFunction.Flag} is registered.
 * The type will be serialized as "SYMBOL".
 * The value will be serialized as the string "LEADING".
 *
 * <p>When we deserialize, we rely on the fact that the registered
 * {@code enum} classes have distinct values. Therefore, knowing that
 * {@code (type="SYMBOL", value="LEADING")} we can convert the string "LEADING"
 * to the enum {@code Flag.LEADING}. */
@SuppressWarnings({"rawtypes", "unchecked"})
public abstract class RelEnumTypes {
  private RelEnumTypes() {}

  private static final ImmutableMap<String, Enum<?>> ENUM_BY_NAME;

  static {
    // Build a mapping from enum constants (e.g. LEADING) to the enum
    // that contains them (e.g. SqlTrimFunction.Flag). If there two
    // enum constants have the same name, the builder will throw.
    final ImmutableMap.Builder<String, Enum<?>> enumByName =
        ImmutableMap.builder();
    register(enumByName, JoinConditionType.class);
    register(enumByName, JoinType.class);
    register(enumByName, SqlExplain.Depth.class);
    register(enumByName, SqlExplainFormat.class);
    register(enumByName, SqlExplainLevel.class);
    register(enumByName, SqlInsertKeyword.class);
    register(enumByName, SqlJsonConstructorNullClause.class);
    register(enumByName, SqlJsonQueryWrapperBehavior.class);
    register(enumByName, SqlJsonValueEmptyOrErrorBehavior.class);
    register(enumByName, SqlMatchRecognize.AfterOption.class);
    register(enumByName, SqlSelectKeyword.class);
    register(enumByName, SqlTrimFunction.Flag.class);
    register(enumByName, TimeUnitRange.class);
    ENUM_BY_NAME = enumByName.build();
  }

  private static void register(ImmutableMap.Builder<String, Enum<?>> builder,
      Class<? extends Enum> aClass) {
    for (Enum enumConstant : aClass.getEnumConstants()) {
      builder.put(enumConstant.name(), enumConstant);
    }
  }

  /** Converts a literal into a value that can be serialized to JSON.
   * In particular, if is an enum, converts it to its name. */
  public static Object fromEnum(Object value) {
    return value instanceof Enum ? fromEnum((Enum) value) : value;
  }

  /** Converts an enum into its name.
   * Throws if the enum's class is not registered. */
  public static String fromEnum(Enum enumValue) {
    if (ENUM_BY_NAME.get(enumValue.name()) != enumValue) {
      throw new AssertionError("cannot serialize enum value to JSON: "
          + enumValue.getDeclaringClass().getCanonicalName() + "."
          + enumValue);
    }
    return enumValue.name();
  }

  /** Converts a string to an enum value.
   * The converse of {@link #fromEnum(Enum)}. */
  static <E extends Enum<E>> E toEnum(String name) {
    return (E) ENUM_BY_NAME.get(name);
  }
}

// End RelEnumTypes.java
